تعلم كيفية تصنيف ومعالجة الأخطاء بفعالية داخل حدود أخطاء React، مما يحسن من استقرار التطبيق وتجربة المستخدم.
تصنيف الأخطاء في حدود أخطاء React: دليل شامل
تُعد معالجة الأخطاء جانبًا حاسمًا في بناء تطبيقات React قوية وقابلة للصيانة. بينما توفر حدود أخطاء React آلية للتعامل بأناقة مع الأخطاء التي تحدث أثناء العرض (rendering)، فإن فهم كيفية تصنيف أنواع الأخطاء المختلفة والاستجابة لها أمر بالغ الأهمية لإنشاء تطبيق مرن حقًا. يستكشف هذا الدليل الأساليب المختلفة لتصنيف الأخطاء داخل حدود الأخطاء، ويقدم أمثلة عملية ورؤى قابلة للتنفيذ لتحسين استراتيجية إدارة الأخطاء لديك.
ما هي حدود أخطاء React؟
تم تقديم حدود الأخطاء (Error Boundaries) في React 16، وهي مكونات React تلتقط أخطاء JavaScript في أي مكان في شجرة المكونات الفرعية الخاصة بها، وتسجل تلك الأخطاء، وتعرض واجهة مستخدم احتياطية بدلاً من انهيار شجرة المكونات بأكملها. تعمل بشكل مشابه لكتلة try...catch، ولكن للمكونات.
الخصائص الرئيسية لحدود الأخطاء:
- معالجة الأخطاء على مستوى المكون: عزل الأخطاء داخل أشجار فرعية محددة من المكونات.
- التدهور التدريجي (Graceful Degradation): منع التطبيق بأكمله من الانهيار بسبب خطأ في مكون واحد.
- واجهة مستخدم احتياطية مُتحكَم بها: عرض رسالة سهلة الاستخدام أو محتوى بديل عند حدوث خطأ.
- تسجيل الأخطاء: تسهيل تتبع الأخطاء وتصحيحها عن طريق تسجيل معلومات الخطأ.
لماذا نصنف الأخطاء في حدود الأخطاء؟
مجرد التقاط الأخطاء لا يكفي. تتطلب المعالجة الفعالة للأخطاء فهم ما حدث بشكل خاطئ والاستجابة وفقًا لذلك. يوفر تصنيف الأخطاء داخل حدود الأخطاء العديد من الفوائد:
- معالجة مستهدفة للأخطاء: قد تتطلب أنواع الأخطاء المختلفة استجابات مختلفة. على سبيل المثال، قد يستدعي خطأ في الشبكة آلية إعادة محاولة، بينما قد يتطلب خطأ في التحقق من صحة البيانات تصحيح الإدخال من قبل المستخدم.
- تحسين تجربة المستخدم: عرض رسائل خطأ أكثر إفادة بناءً على نوع الخطأ. رسالة عامة مثل "حدث خطأ ما" أقل فائدة من رسالة محددة تشير إلى مشكلة في الشبكة أو إدخال غير صالح.
- تعزيز تصحيح الأخطاء: يوفر تصنيف الأخطاء سياقًا قيمًا لتصحيح الأخطاء وتحديد السبب الجذري للمشكلات.
- المراقبة الاستباقية: تتبع تكرار أنواع الأخطاء المختلفة لتحديد المشاكل المتكررة وتحديد أولويات الإصلاحات.
- واجهة مستخدم احتياطية استراتيجية: عرض واجهات مستخدم احتياطية مختلفة اعتمادًا على الخطأ، مما يوفر معلومات أو إجراءات أكثر صلة للمستخدم.
أساليب تصنيف الأخطاء
يمكن استخدام عدة تقنيات لتصنيف الأخطاء داخل حدود أخطاء React:
1. استخدام instanceof
يتحقق عامل instanceof مما إذا كان الكائن مثيلاً (instance) لفئة معينة. هذا مفيد لتصنيف الأخطاء بناءً على أنواع الأخطاء المضمنة أو المخصصة.
مثال:
class NetworkError extends Error {
constructor(message) {
super(message);
this.name = "NetworkError";
}
}
class ValidationError extends Error {
constructor(message) {
super(message);
this.name = "ValidationError";
}
}
class MyErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
// تحديث الحالة حتى يُظهر العرض التالي واجهة المستخدم الاحتياطية.
return { hasError: true, error: error };
}
componentDidCatch(error, errorInfo) {
// يمكنك أيضًا تسجيل الخطأ في خدمة تقارير الأخطاء
console.error("Caught error:", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
// يمكنك عرض أي واجهة مستخدم احتياطية مخصصة
let errorMessage = "حدث خطأ ما.";
if (this.state.error instanceof NetworkError) {
errorMessage = "حدث خطأ في الشبكة. يرجى التحقق من اتصالك والمحاولة مرة أخرى.";
} else if (this.state.error instanceof ValidationError) {
errorMessage = "حدث خطأ في التحقق من الصحة. يرجى مراجعة إدخالك.";
}
return (
<div>
<h2>خطأ!</h2>
<p>{errorMessage}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
شرح:
- تم تعريف فئات مخصصة
NetworkErrorوValidationError، والتي ترث من فئةErrorالمضمنة. - في دالة
renderلمكونMyErrorBoundary، يتم استخدام عاملinstanceofللتحقق من نوع الخطأ الذي تم التقاطه. - بناءً على نوع الخطأ، يتم عرض رسالة خطأ محددة في واجهة المستخدم الاحتياطية.
2. استخدام أكواد أو خصائص الخطأ
هناك طريقة أخرى وهي تضمين أكواد أو خصائص الخطأ داخل كائن الخطأ نفسه. وهذا يسمح بتصنيف أكثر دقة بناءً على سيناريوهات أخطاء محددة.
مثال:
function fetchData(url) {
return new Promise((resolve, reject) => {
fetch(url)
.then(response => {
if (!response.ok) {
const error = new Error("Network request failed");
error.code = response.status; // إضافة كود خطأ مخصص
reject(error);
}
return response.json();
})
.then(data => resolve(data))
.catch(error => reject(error));
});
}
class MyErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, error: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
// تحديث الحالة حتى يُظهر العرض التالي واجهة المستخدم الاحتياطية.
return { hasError: true, error: error };
}
componentDidCatch(error, errorInfo) {
// يمكنك أيضًا تسجيل الخطأ في خدمة تقارير الأخطاء
console.error("Caught error:", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
let errorMessage = "حدث خطأ ما.";
if (this.state.error.code === 404) {
errorMessage = "المورد غير موجود.";
} else if (this.state.error.code >= 500) {
errorMessage = "خطأ في الخادم. يرجى المحاولة مرة أخرى لاحقًا.";
}
return (
<div>
<h2>خطأ!</h2>
<p>{errorMessage}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.error && this.state.error.toString()}<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
شرح:
- تضيف دالة
fetchDataخاصيةcodeإلى كائن الخطأ، تمثل رمز حالة HTTP. - يتحقق مكون
MyErrorBoundaryمن خاصيةcodeلتحديد سيناريو الخطأ المحدد. - يتم عرض رسائل خطأ مختلفة بناءً على كود الخطأ.
3. استخدام خريطة أخطاء مركزية
بالنسبة للتطبيقات المعقدة، يمكن أن يؤدي الحفاظ على خريطة أخطاء مركزية إلى تحسين تنظيم الكود وقابلية الصيانة. يتضمن ذلك إنشاء قاموس أو كائن يربط أنواع الأخطاء أو الأكواد برسائل خطأ ومنطق معالجة محدد.
مثال:
const errorMap = {
"NETWORK_ERROR": {
message: "حدث خطأ في الشبكة. يرجى التحقق من اتصالك.",
retry: true,
},
"INVALID_INPUT": {
message: "إدخال غير صالح. يرجى مراجعة بياناتك.",
retry: false,
},
404: {
message: "المورد غير موجود.",
retry: false,
},
500: {
message: "خطأ في الخادم. يرجى المحاولة مرة أخرى لاحقًا.",
retry: true,
},
"DEFAULT": {
message: "حدث خطأ ما.",
retry: false,
},
};
function handleCustomError(errorType) {
const errorDetails = errorMap[errorType] || errorMap["DEFAULT"];
return errorDetails;
}
class MyErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, errorDetails: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
// تحديث الحالة حتى يُظهر العرض التالي واجهة المستخدم الاحتياطية.
const errorDetails = handleCustomError(error.message);
return { hasError: true, errorDetails: errorDetails };
}
componentDidCatch(error, errorInfo) {
// يمكنك أيضًا تسجيل الخطأ في خدمة تقارير الأخطاء
console.error("Caught error:", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
const { message } = this.state.errorDetails;
return (
<div>
<h2>خطأ!</h2>
<p>{message}</p>
<details style={{ whiteSpace: 'pre-wrap' }}>
{this.state.errorDetails.message}<br />
{this.state.errorInfo.componentStack}
</details>
</div>
);
}
return this.props.children;
}
}
function MyComponent(){
const [data, setData] = React.useState(null);
React.useEffect(() => {
try {
throw new Error("NETWORK_ERROR");
} catch (e) {
throw e;
}
}, []);
return <div></div>;
}
شرح:
- يخزن كائن
errorMapمعلومات الخطأ، بما في ذلك الرسائل وعلامات إعادة المحاولة، بناءً على أنواع الأخطاء أو الأكواد. - تقوم دالة
handleCustomErrorبجلب تفاصيل الخطأ منerrorMapبناءً على رسالة الخطأ وتعيد القيم الافتراضية إذا لم يتم العثور على كود محدد. - يستخدم مكون
MyErrorBoundaryدالةhandleCustomErrorللحصول على رسالة الخطأ المناسبة منerrorMap.
أفضل الممارسات لتصنيف الأخطاء
- تحديد أنواع أخطاء واضحة: ضع مجموعة متسقة من أنواع الأخطاء أو الأكواد لتطبيقك.
- توفير معلومات سياقية: قم بتضمين التفاصيل ذات الصلة في كائنات الخطأ لتسهيل عملية التصحيح.
- مركزية منطق معالجة الأخطاء: استخدم خريطة أخطاء مركزية أو دوال مساعدة لإدارة معالجة الأخطاء بشكل متسق.
- تسجيل الأخطاء بفعالية: تكامل مع خدمات الإبلاغ عن الأخطاء لتتبع وتحليل الأخطاء في بيئة الإنتاج. تشمل الخدمات الشائعة Sentry و Rollbar و Bugsnag.
- اختبار معالجة الأخطاء: اكتب اختبارات الوحدة للتحقق من أن حدود الأخطاء الخاصة بك تتعامل بشكل صحيح مع أنواع الأخطاء المختلفة.
- مراعاة تجربة المستخدم: اعرض رسائل خطأ إعلامية وسهلة الاستخدام ترشد المستخدمين نحو الحل. تجنب المصطلحات التقنية.
- مراقبة معدلات الأخطاء: تتبع تكرار أنواع الأخطاء المختلفة لتحديد المشاكل المتكررة وتحديد أولويات الإصلاحات.
- التدويل (i18n): عند تقديم رسائل الخطأ للمستخدم، تأكد من أن رسائلك مدولة بشكل صحيح لدعم اللغات والثقافات المختلفة. استخدم مكتبات مثل
i18nextأو React's Context API لإدارة الترجمات. - إمكانية الوصول (a11y): تأكد من أن رسائل الخطأ الخاصة بك متاحة للمستخدمين ذوي الإعاقة. استخدم سمات ARIA لتوفير سياق إضافي لقارئات الشاشة.
- الأمان: كن حذرًا بشأن المعلومات التي تعرضها في رسائل الخطأ، خاصة في بيئات الإنتاج. تجنب كشف البيانات الحساسة التي يمكن أن يستغلها المهاجمون. على سبيل المثال، لا تعرض تتبعات المكدس (stack traces) الخام للمستخدمين النهائيين.
سيناريو مثال: معالجة أخطاء API في تطبيق للتجارة الإلكترونية
لنفترض أن لدينا تطبيقًا للتجارة الإلكترونية يجلب معلومات المنتج من واجهة برمجة تطبيقات (API). تشمل سيناريوهات الأخطاء المحتملة ما يلي:
- أخطاء الشبكة: خادم API غير متاح أو انقطاع اتصال الإنترنت لدى المستخدم.
- أخطاء المصادقة: رمز المصادقة الخاص بالمستخدم غير صالح أو منتهي الصلاحية.
- أخطاء عدم العثور على المورد: المنتج المطلوب غير موجود.
- أخطاء الخادم: يواجه خادم API خطأ داخليًا.
باستخدام حدود الأخطاء وتصنيفها، يمكن للتطبيق التعامل مع هذه السيناريوهات بأناقة:
// مثال (مبسط)
async function fetchProduct(productId) {
try {
const response = await fetch(`/api/products/${productId}`);
if (!response.ok) {
if (response.status === 404) {
throw new Error("PRODUCT_NOT_FOUND");
} else if (response.status === 401 || response.status === 403) {
throw new Error("AUTHENTICATION_ERROR");
} else {
throw new Error("SERVER_ERROR");
}
}
return await response.json();
} catch (error) {
if (error instanceof TypeError && error.message === "Failed to fetch") {
throw new Error("NETWORK_ERROR");
}
throw error;
}
}
class ProductErrorBoundary extends React.Component {
constructor(props) {
super(props);
this.state = { hasError: false, errorDetails: null, errorInfo: null };
}
static getDerivedStateFromError(error) {
const errorDetails = handleCustomError(error.message); // استخدم errorMap كما هو موضح سابقًا
return { hasError: true, errorDetails: errorDetails };
}
componentDidCatch(error, errorInfo) {
console.error("Caught error:", error, errorInfo);
this.setState({errorInfo: errorInfo});
}
render() {
if (this.state.hasError) {
const { message, retry } = this.state.errorDetails;
return (
<div>
<h2>خطأ!</h2>
<p>{message}</p>
{retry && <button onClick={() => window.location.reload()}>إعادة المحاولة</button>}
</div>
);
}
return this.props.children;
}
}
شرح:
- تتحقق دالة
fetchProductمن رمز حالة استجابة API وتطلق أنواع أخطاء محددة بناءً على الحالة. - يلتقط مكون
ProductErrorBoundaryهذه الأخطاء ويعرض رسائل الخطأ المناسبة. - بالنسبة لأخطاء الشبكة وأخطاء الخادم، يتم عرض زر "إعادة المحاولة"، مما يسمح للمستخدم بمحاولة الطلب مرة أخرى.
- بالنسبة لأخطاء المصادقة، قد يتم إعادة توجيه المستخدم إلى صفحة تسجيل الدخول.
- بالنسبة لأخطاء عدم العثور على المورد، يتم عرض رسالة تشير إلى أن المنتج غير موجود.
الخاتمة
يعد تصنيف الأخطاء داخل حدود أخطاء React أمرًا ضروريًا لبناء تطبيقات مرنة وسهلة الاستخدام. من خلال استخدام تقنيات مثل التحقق بـ instanceof، وأكواد الأخطاء، وخرائط الأخطاء المركزية، يمكنك التعامل بفعالية مع سيناريوهات الأخطاء المختلفة وتوفير تجربة مستخدم أفضل. تذكر اتباع أفضل الممارسات لمعالجة الأخطاء وتسجيلها واختبارها لضمان أن تطبيقك يتعامل بأناقة مع المواقف غير المتوقعة.
من خلال تنفيذ هذه الاستراتيجيات، يمكنك تحسين استقرار تطبيقات React وقابليتها للصيانة بشكل كبير، مما يوفر تجربة أكثر سلاسة وموثوقية للمستخدمين، بغض النظر عن موقعهم أو خلفيتهم.
مصادر إضافية: